﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

using OmniKits.Extensions;

namespace OmniKits.Core
{
    public abstract class ExeConfigurationMonitorBase : DisposableBase
    {
#pragma warning disable 1591
        private volatile Configuration _CachedConfig = null;

        private FileSystemWatcher _ExeWatcher = null;
        private FileSystemWatcher _ExeRoamingWatcher = null;
        private FileSystemWatcher _ExeLocalWatcher = null;

        public ExeConfigurationFileMap ConfigurationFileMap { get; }

        public ConfigurationUserLevel UserLevel { get; }

        public int MaxRetry { get; set; } = 3;
        public TimeSpan RetryDelayInitial { get; set; } = TimeSpan.FromMilliseconds(300);
        public TimeSpan? RetryDelayIncrement { get; set; } = TimeSpan.FromMilliseconds(100);
        public TimeSpan? RetryDelayMaximal { get; set; } = null;

        public Configuration ActiveConfiguration => GetOrCreateConfig();

        private Configuration GetOrCreateConfig()
        {
            var config = GetConfig();
            if (GetConfigurationValid(config))
                return config;

            SaveDefaultConfig();
            return GetOrCreateConfig();
        }

        private Configuration GetConfig()
        {
            var cached = _CachedConfig;
            if (cached != null) return cached;

            cached = LoadUserConfig();
            _CachedConfig = cached;
            return cached;
        }

        private Configuration LoadUserConfig()
        {
            return Retry.DoRetry(() =>
            {
                return ConfigurationManager.OpenMappedExeConfiguration(ConfigurationFileMap, UserLevel);
            }, MaxRetry, RetryDelayInitial, RetryDelayIncrement, RetryDelayMaximal);
        }

        protected abstract bool GetConfigurationValid(Configuration config);

        public void SaveDefaultConfig()
        {
            var config = ConfigurationManager.OpenMappedExeConfiguration(
                new ExeConfigurationFileMap
                {
                    ExeConfigFilename = ConfigurationFileMap.ExeConfigFilename,
                    RoamingUserConfigFilename = ConfigurationFileMap.ExeConfigFilename,
                }, ConfigurationUserLevel.PerUserRoaming);

            ResetConfiguration(config);
            if (!GetConfigurationValid(config))
                throw new InvalidDataException();

            _CachedConfig = null;

            config.Save(ConfigurationSaveMode.Full);
            LoadUserConfig().Save(ConfigurationSaveMode.Full, true);
        }

        public abstract void ResetConfiguration(Configuration config);

        public bool IsEnabled
        {
            get
            {
                return _ExeWatcher.EnableRaisingEvents;
            }
            set
            {
                _ExeWatcher.EnableRaisingEvents = value;

                var roamingWatcher = _ExeRoamingWatcher;
                if (roamingWatcher != null)
                    roamingWatcher.EnableRaisingEvents = value;

                var localWatcher = _ExeLocalWatcher;
                if (localWatcher != null)
                    localWatcher.EnableRaisingEvents = value;
            }
        }

        public ExeConfigurationMonitorBase(ExeConfigurationFileMap configFileMap)
        {
            ConfigurationFileMap = configFileMap;

            if (configFileMap.ExeConfigFilename.IsNullOrWhiteSpace())
            {
                throw new ArgumentException(null, nameof(configFileMap));
            }
            else
            {
                _ExeWatcher = MakeWatcher(configFileMap.ExeConfigFilename);
                UserLevel = ConfigurationUserLevel.None;
            }

            if (!configFileMap.RoamingUserConfigFilename.IsNullOrWhiteSpace())
            {
                _ExeRoamingWatcher = MakeWatcher(configFileMap.RoamingUserConfigFilename);
                UserLevel = ConfigurationUserLevel.PerUserRoaming;
            }
            if (!configFileMap.LocalUserConfigFilename.IsNullOrWhiteSpace())
            {
                _ExeLocalWatcher = MakeWatcher(configFileMap.LocalUserConfigFilename);
                UserLevel = ConfigurationUserLevel.PerUserRoamingAndLocal;
            }
        }

        private FileSystemWatcher MakeWatcher(string path)
        {
            var dir = Path.GetDirectoryName(path);
            Directory.CreateDirectory(dir);
            var watcher = new FileSystemWatcher(dir, Path.GetFileName(path));

            watcher.Created += delegate { RefreshConfiguration(); };
            watcher.Changed += delegate { RefreshConfiguration(); };
            watcher.Renamed += (s, e) =>
            {
                if (File.Exists(path))
                    RefreshConfiguration();
            };

            return watcher;
        }

        public void RefreshConfiguration()
        {
            _CachedConfig = null;
            OnConfigurationUpdated();
        }

        protected abstract void OnConfigurationUpdated();

        protected override void Dispose(bool disposing)
        {
            if (!disposing) return;

            _ExeWatcher.Dispose();
            _ExeLocalWatcher?.Dispose();
            _ExeRoamingWatcher?.Dispose();
        }
    }
}
